package start.spring.basic.dynamic;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Repository;
import org.springframework.util.ReflectionUtils;

import com.alibaba.fastjson.JSON;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import start.spring.basic.exception.common.RedisException;
import start.spring.basic.redis.RedisKey;
import start.spring.basic.util.common.StringUtil;

/**
 * 动态redis 操作类
 *
 * @author xyy
 */
@Repository
public class DynamicRedisDAO {
	
    @Autowired
    DynamicRedisRegister redisRegister;

    /**
     * 用于非主从redis操作
     *	
     * @param redisKey
     * @return
     * @since           1.0
     */
    private StringRedisTemplate getRedis(String redisKey) {
    	if(StringUtil.isEmpty(redisKey)) {
    		redisKey = RedisKey.REDIS_DEFAULT;
    	}
    	return redisRegister.getRedisMap(redisKey).get(redisKey);
    }

    /**
     * 从redis获取数据
     *
     * @param key
     * @return
     * @throws RedisException 自定义redis异常
     */
    public String get(String redisKey, String key) throws RedisException {
        String result = null;
        try {
            result = this.getRedis(redisKey).opsForValue().get(key);
        } catch (Exception e) {
            this.handleException(e);
        }
        return result;
    }
    
    /**
     * 获得对象
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     * 
     */
    public <T> T getObject(String redisKey, String key, Class<T> clazz) {
    	T result = null;
        try {
            result = JSON.parseObject(this.getRedis(redisKey).opsForValue().get(key),clazz);
        } catch (Exception e) {
            this.handleException(e);
        }
        return result;
    }
    

    /**
     * 向redis设置值，不超时
     *
     * @param key
     * @param value
     * @throws RedisException 自定义redis异常
     */
    public void set(String redisKey, String key, String value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForValue().set(key, value);
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setObject(String redisKey, String key, T value) {
    	try {
    		this.getRedis(redisKey).opsForValue().set(key, JSON.toJSONString(value));
        } catch (Exception e) {
            this.handleException(e);
        }
    }
    
    /**
     * 缓存对象列表
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setObjectList(String redisKey, final String key, final List<T> objectDataList) {
    	Long count = null;
    	try {
    		if(objectDataList!=null&&objectDataList.size()>0) {
    			List<String> stringDataList = new ArrayList<String>(objectDataList.size());
    			for(T object:objectDataList) {
    				stringDataList.add(JSON.toJSONString(object));
    			}
    			count = this.getRedis(redisKey).opsForList().rightPushAll(key, stringDataList);
    		}
        } catch (Exception e) {
            this.handleException(e);
        }
        return count == null ? 0 : count;
    }
    
    /**
     * 获得列表对象
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     * 
     */
    public <T> List<T> getObjectList(String redisKey, String key, Class<T> clazz) {
    	List<T> resultObjectList = null;
        try {
        	List<String> resultStringList = this.getRedis(redisKey).opsForList().range(key, 0, -1);
            if(resultStringList!=null&&resultStringList.size()>0) {
            	resultObjectList = new ArrayList<>();
            	for(String value:resultStringList) {
            		resultObjectList.add(JSON.parseObject(value,clazz));
            	}
            }
        } catch (Exception e) {
            this.handleException(e);
        }
        return resultObjectList;
    }
    
    /**
     * 设置值及对应超时时间
     *
     * @param key
     * @param value
     * @param time  超时时间，单位秒
     * @throws RedisException 自定义redis异常
     */
    public void setex(String redisKey, String key, String value, long time) throws RedisException {
        try {
            this.getRedis(redisKey).opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } catch (Exception e) {
            this.handleException(e);
        }
    }
    
    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setObjectEx(String redisKey, String key, T value, long time) {
    	try {
    		this.getRedis(redisKey).opsForValue().set(key, JSON.toJSONString(value), time, TimeUnit.SECONDS);
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 锁定值
     *
     * @param key
     * @param value
     * @throws RedisException 自定义redis异常
     */
    public boolean setnx(String redisKey, String key, String value) throws RedisException {
        boolean lock = false;
        try {
            Boolean lockResult = this.getRedis(redisKey).opsForValue().setIfAbsent(key, value);
            if (lockResult != null && lockResult) {
                lock = true;
            }
        } catch (Exception e) {
            this.handleException(e);
        }
        return lock;
    }
    
    /**
     * 锁定对象
     *
     * @param key
     * @param value
     * @throws RedisException 自定义redis异常
     */
    public <T> boolean setObjectNx(String redisKey, String key, T value) throws RedisException {
        boolean lock = false;
        try {
            Boolean lockResult = this.getRedis(redisKey).opsForValue().setIfAbsent(key, JSON.toJSONString(value));
            if (lockResult != null && lockResult) {
                lock = true;
            }
        } catch (Exception e) {
            this.handleException(e);
        }
        return lock;
    }

    /**
     * 获取key的超时时间
     *
     * @param key
     * @throws RedisException 自定义redis异常
     */
    public long ttl(String redisKey, String key) throws RedisException {
        long result = 0;
        try {
            result = this.getRedis(redisKey).getExpire(key);
        } catch (Exception e) {
            this.handleException(e);
        }
        return result;
    }

    /**
     * 设置key超时
     *
     * @param key
     * @param time
     * @throws RedisException 自定义redis异常
     */
    public void expire(String redisKey, String key, long time) throws RedisException {
        try {
            this.getRedis(redisKey).expire(key, time, TimeUnit.SECONDS);
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 把值存入队列
     *
     * @param key   队列key
     * @param value 存入的值
     * @throws RedisException 自定义redis异常
     */
    public void lpush(String redisKey, String key, String value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForList().leftPush(key, value);
        } catch (Exception e) {
            this.handleException(e);
        }
    }
    
    /**
     * 把对象存入队列
     *
     * @param key   队列key
     * @param value 存入的对象
     * 
     * @throws RedisException 自定义redis异常
     */
    public <T> void lPushObject(String redisKey, String key, T value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForList().leftPush(key, JSON.toJSONString(value));
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 把值存入队列
     *
     * @param key   队列key
     * @param value 存入的值
     * @throws RedisException 自定义redis异常
     */
    public void rpush(String redisKey, String key, String value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForList().rightPush(key, value);
        } catch (Exception e) {
            this.handleException(e);
        }
    }
    
    /**
     * 把值存入队列
     *
     * @param key   队列key
     * @param value 存入的值
     * @throws RedisException 自定义redis异常
     */
    public <T> void rPushObject(String redisKey, String key, T value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForList().rightPush(key, JSON.toJSONString(value));
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 从队列中取值
     *
     * @param key 队列key
     * @throws RedisException 自定义redis异常
     */
    public String rpop(String redisKey, String key) throws RedisException {
        String listOne = null;
        try {
            listOne = this.getRedis(redisKey).opsForList().rightPop(key);
        } catch (Exception e) {
            this.handleException(e);
        }
        return listOne;
    }

    /**
     * 判断队列长度
     *
     * @param key 队列key
     * @throws RedisException 自定义redis异常
     */
    public Long llen(String redisKey, String key) throws RedisException {
        Long size = null;
        try {
            size = this.getRedis(redisKey).opsForList().size(key);
        } catch (Exception e) {
            this.handleException(e);
        }
        return size;
    }

    /**
     * 返回指定范围内的数据
     *
     * @param redisKey
     * @param key
     * @param index
     * @param size
     * @return
     * @throws RedisException
     * @since 1.0
     */
    public List<String> range(String redisKey, String key, long start, long end) throws RedisException {
        List<String> redisList = null;
        try {
            redisList = this.getRedis(redisKey).opsForList().range(key, start, end);
        } catch (Exception e) {
            this.handleException(e);
        }
        return redisList;
    }
    
    /**
     * 返回指定范围内的数据
     *
     * @param redisKey
     * @param key
     * @param index
     * @param size
     * @return
     * @throws RedisException
     * @since 1.0
     */
    public <T> List<T> rangeObject(String redisKey, String key, long start, long end, Class<T> clazz) throws RedisException {
        List<T> redisObjectList = null;
        try {
        	List<String> redisStringList = this.getRedis(redisKey).opsForList().range(key, start, end);
        	if(redisStringList!=null&&redisStringList.size()>0) {
        		redisObjectList = new ArrayList<>(redisStringList.size());
        		for(String value:redisStringList) {
        			redisObjectList.add(JSON.parseObject(value,clazz));
            	}
        	}
        } catch (Exception e) {
            this.handleException(e);
        }
        return redisObjectList;
    }

    /**
     * 删除指定范围内的日志
     *
     * @param redisKey
     * @param key
     * @param index
     * @param size
     * @return
     * @throws RedisException
     * @since 1.0
     */
    public void trim(String redisKey, String key, long start, long end) throws RedisException {
        try {
            this.getRedis(redisKey).opsForList().trim(key, start, end);
        } catch (Exception e) {
            this.handleException(e);
        }
    }
    
    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(String redisKey, String pattern) {
    	Set<String> keysSet = null;
    	try {
    		keysSet = this.getRedis(redisKey).keys(pattern);
        } catch (Exception e) {
            this.handleException(e);
        }
        return keysSet;
    }

    /**
     * 查询key是否存在
     *
     * @param key
     * @return
     * @throws RedisException 自定义redis异常
     */
    public boolean hasKey(String redisKey, String key) throws RedisException {
        boolean hasKey = false;
        try {
            hasKey = this.getRedis(redisKey).hasKey(key);
        } catch (Exception e) {
            this.handleException(e);
        }
        return hasKey;
    }

    /**
     * 以hash形式存储数据
     *
     * @param key
     * @param valueMap
     * @throws RedisException 自定义redis异常
     */
    public void putMap(String redisKey, String key, Map<String, String> valueMap) throws RedisException {
        try {
            this.getRedis(redisKey).opsForHash().putAll(key, valueMap);
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 修改hash key中某一个map key的值
     *
     * @param key
     * @param mapKey
     * @param value
     * @throws RedisException 自定义redis异常
     */
    public void putMapKey(String redisKey, String key, String mapKey, String value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForHash().put(key, mapKey, value);
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 查询hash map
     *
     * @param key
     * @return
     * @throws RedisException 自定义redis异常
     */
    public Map<Object, Object> getMap(String redisKey, String key) throws RedisException {
        Map<Object, Object> resultMap = null;
        try {
            resultMap = this.getRedis(redisKey).opsForHash().entries(key);
        } catch (Exception e) {
            this.handleException(e);
        }
        return resultMap;
    }

    /**
     * 查询 hash map中某个hash key对应的值
     *
     * @param key
     * @param hashKey
     * @return
     * @throws RedisException 自定义redis异常
     */
    public Object getMapValue(String redisKey, String key, String hashKey) throws RedisException {
        Object value = null;
        try {
            value = this.getRedis(redisKey).opsForHash().get(key, hashKey);
        } catch (Exception e) {
            this.handleException(e);
        }
        return value;
    }

    /**
     * 获取某个key剩余的有效时间
     *
     * @param key 待获取的key
     * @return
     * @throws RedisException
     * @since 1.0
     */
    public long getKeyTime(String redisKey, String key) throws RedisException {
        long time = 0;
        try {
            time = this.getRedis(redisKey).getExpire(key, TimeUnit.SECONDS);
        } catch (Exception e) {
            this.handleException(e);
        }
        return time;
    }

    /**
     * 通过管道批量插入数据到redis，增加插入效率
     *
     * @param keys  待插入的redis key的列表
     * @param datas 对应key的值列表
     * @return 插入记录数量
     * @since 1.0
     */
    public long batchInsertMap(String redisKey, List<String> keys, List<Map<String, String>> datas) {
        long insertRecode = 0;
        Jedis jedis = null;
        try {

            Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
            ReflectionUtils.makeAccessible(jedisField);
            jedis = (Jedis) ReflectionUtils.getField(jedisField, getRedis(redisKey).getConnectionFactory().getConnection());

            //创建管道
            Pipeline pipeline = jedis.pipelined();
            if (keys != null && keys.size() > 0 &&
                    datas != null && keys.size() == datas.size()) {
                RedisSerializer<String> serializer = getRedis(redisKey).getStringSerializer();
                for (int i = 0; i < datas.size(); i++) {
                    Map<byte[], byte[]> byteDataMap = new HashMap<byte[], byte[]>(16);

                    //转换对应的key为byte[]
                    byte[] key = serializer.serialize(keys.get(i));

                    Map<String, String> data = datas.get(i);
                    Set<String> mapKeySet = data.keySet();
                    Iterator<String> it = mapKeySet.iterator();
                    while (it.hasNext()) {
                        String mapKey = it.next();
                        String mapValue = data.get(mapKey);
                        byteDataMap.put(serializer.serialize(mapKey),
                                serializer.serialize(mapValue));
                    }
                    pipeline.hmset(key, byteDataMap);
                }
            }
            List<Object> objects = pipeline.syncAndReturnAll();
            insertRecode = objects.size();
            pipeline.close();
        } catch (Exception e) {
            this.handleException(e);
        } finally {
            if(jedis != null){
                jedis.close();
            }
        }
        return insertRecode;
    }

    /**
     * 通过管道批量插入数据到redis，增加插入效率，每个key可以单独设置超时时间
     *
     * @param keys  待插入的redis key的列表
     * @param datas 对应key的值列表
     * @param times 对应key的超时时间
     * @return 插入记录数量
     * @since 1.0
     */
    public long batchInsertMapExprie(String redisKey, List<String> keys, List<Map<String, String>> datas, List<Long> times) {
        long insertRecode = 0;
        Jedis jedis = null;
        try {
            Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
            ReflectionUtils.makeAccessible(jedisField);
            jedis = (Jedis) ReflectionUtils.getField(jedisField, getRedis(redisKey).getConnectionFactory().getConnection());

            //创建管道
            Pipeline pipeline = jedis.pipelined();

            if (keys != null && keys.size() > 0 &&
                    datas != null && keys.size() == datas.size()) {
                RedisSerializer<String> serializer = getRedis(redisKey).getStringSerializer();
                for (int i = 0; i < datas.size(); i++) {

                    if (times.get(i) > 0) {
                        Map<byte[], byte[]> byteDataMap = new HashMap<byte[], byte[]>(16);

                        //转换对应的key为byte[]

                        byte[] key = serializer.serialize(keys.get(i));

                        Map<String, String> data = datas.get(i);
                        Set<String> mapKeySet = data.keySet();
                        Iterator<String> it = mapKeySet.iterator();
                        while (it.hasNext()) {
                            String mapKey = it.next();
                            String mapValue = data.get(mapKey);
                            byteDataMap.put(serializer.serialize(mapKey),
                                    serializer.serialize(mapValue));
                        }
                        pipeline.hmset(key, byteDataMap);
                        pipeline.expire(key, Integer.parseInt(String.valueOf(times.get(i))));
                    }
                }
            }
            List<Object> objects = pipeline.syncAndReturnAll();
            insertRecode = objects.size();
            pipeline.close();
        } catch (Exception e) {
            this.handleException(e);
        } finally {
            if(jedis != null){
                jedis.close();
            }
        }
        return insertRecode;
    }

    /**
     * 通过管道批量更新redis数据，增加更新效率
     *
     * @param keys  待插入的redis key的列表
     * @param datas 对应key的值列表
     * @return 插入记录数量
     * @since 1.0
     */
    public long batchUpdateMap(String redisKey, List<String> keys, List<Map<String, String>> datas) {
        long updateRecode = 0;
        Jedis jedis = null;
        try {
            Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
            ReflectionUtils.makeAccessible(jedisField);
            jedis = (Jedis) ReflectionUtils.getField(jedisField, getRedis(redisKey).getConnectionFactory().getConnection());

            //创建管道
            Pipeline pipeline = jedis.pipelined();
            if (keys != null && keys.size() > 0 &&
                    datas != null && keys.size() == datas.size()) {
                RedisSerializer<String> serializer = getRedis(redisKey).getStringSerializer();
                for (int i = 0; i < datas.size(); i++) {
                    //转换对应的key为byte[]
                    byte[] key = serializer.serialize(keys.get(i));

                    Map<String, String> data = datas.get(i);
                    Set<String> mapKeySet = data.keySet();
                    Iterator<String> it = mapKeySet.iterator();
                    while (it.hasNext()) {
                        String mapKey = it.next();
                        String mapValue = data.get(mapKey);
                        pipeline.hset(key, serializer.serialize(mapKey),
                                serializer.serialize(mapValue));
                    }
                }
            }
            List<Object> objects = pipeline.syncAndReturnAll();
            updateRecode = objects.size();
            pipeline.close();
        } catch (Exception e) {
            this.handleException(e);
        } finally {
            if(jedis != null){
                jedis.close();
            }
        }
        return updateRecode;
    }

    public void hashIncrBy(String redisKey, String key, String hashKey, long value) throws RedisException {
        try {
            this.getRedis(redisKey).opsForHash().increment(key, hashKey, value);
        } catch (Exception e) {
            this.handleException(e);
        }
    }

    /**
     * 公共redis异常处理
     *
     * @param e
     * @since 1.0
     */
    private void handleException(Exception e) {
        Throwable th = e.getCause();
        throw new RedisException(e.toString(), th);
    }

    /**
     * 获取redis服务器时间
     * @param redisKey
     * @return
     */
    public String getRedisTime(String redisKey) {
        String strRedistime = "";
        Jedis jedis = null;
        try {
            Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
            ReflectionUtils.makeAccessible(jedisField);
            jedis = (Jedis) ReflectionUtils.getField(jedisField, getRedis(redisKey).getConnectionFactory().getConnection());

            List<String> list = jedis.time();
            String strSecond = list.get(0);         //第一行为以 UNIX 时间戳格式表示已经过去的秒数
            String strMilliSecond = list.get(1);    //第二行为当前这一秒已经过去的微秒数
            StringBuffer sbMilliSecond = new StringBuffer();
            if (strMilliSecond == null || strMilliSecond.length() == 0){
                strMilliSecond = "000000";
            }else {
                int count = 6 - strMilliSecond.length();// 与6为之间的差，也就是需要补几个0
                for (int i = 0; i < count; i++) {
                    sbMilliSecond.append("0");
                }
                strMilliSecond = sbMilliSecond.toString() + strMilliSecond;
            }
            strRedistime = strSecond + strMilliSecond.substring(0, 3);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(jedis != null){
                jedis.close();
            }
        }
        return strRedistime;
    }

}
